package net.wicp.tams.common.binlog.self.event;

import java.math.BigDecimal;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Date;

import lombok.extern.slf4j.Slf4j;
import net.wicp.tams.common.apiext.ByteUtil;
import net.wicp.tams.common.apiext.StringUtil;
import net.wicp.tams.common.binlog.self.IBinlogRead;
import net.wicp.tams.common.binlog.self.IEventRead;
import net.wicp.tams.common.binlog.self.LogBuffer;
import net.wicp.tams.common.binlog.self.MysqlUtil;
import net.wicp.tams.common.binlog.self.bean.EventBean;
import net.wicp.tams.common.binlog.self.callback.ICol;
import net.wicp.tams.common.binlog.self.callback.column.ColBigDecimal;
import net.wicp.tams.common.binlog.self.callback.column.ColBytes;
import net.wicp.tams.common.binlog.self.callback.column.ColDateSql;
import net.wicp.tams.common.binlog.self.callback.column.ColDateTime;
import net.wicp.tams.common.binlog.self.callback.column.ColDouble;
import net.wicp.tams.common.binlog.self.callback.column.ColFloat;
import net.wicp.tams.common.binlog.self.callback.column.ColInteger;
import net.wicp.tams.common.binlog.self.callback.column.ColLong;
import net.wicp.tams.common.binlog.self.callback.column.ColNull;
import net.wicp.tams.common.binlog.self.callback.column.ColString;
import net.wicp.tams.common.binlog.self.callback.column.ColTimeSql;
import net.wicp.tams.common.binlog.self.callback.column.ColTimestamp;
import net.wicp.tams.common.binlog.self.constant.ColumnType;

@Slf4j
public abstract class AbsEvent implements IEventRead {
	protected final EventBean event;

	protected final LogBuffer logBuffer;

	private Calendar cal;

	protected String charsetName = "UTF-8";

	// public static ThreadLocal<Host> hosts = new ThreadLocal<Host>();

	public AbsEvent(EventBean event) {
		this.event = event;
		logBuffer = event.getBody();
		log.info("start：{},type：{}", row(event.getBeginHead()), event.getHead().getEventType().name());
	}

	public void rowlog(String info) {
		long temp = event.getBeginBody() + logBuffer.position();
		log.info("{}:{}", StringUtil.hasNull(info, "当前位置"), row(temp));
	}

	public static String stringFill(String source, int fillLength, char fillChar, boolean isLeftFill) {
		if ((source == null) || (source.length() >= fillLength))
			return source;

		StringBuilder result = new StringBuilder(fillLength);
		int len = fillLength - source.length();
		if (isLeftFill) {
			for (; len > 0; --len) {
				result.append(fillChar);
			}
			result.append(source);
		} else {
			result.append(source);
			for (; len > 0; --len) {
				result.append(fillChar);
			}
		}
		return result.toString();
	}

	// 解析列字段
	@SuppressWarnings("rawtypes")
	protected ICol paseCol(ColumnType colType, String colName, final int meta, boolean isnull) {
		if (isnull) {
			return ColNull.builder().colName(colName).colType(colType).value(null).build();
		}
		if (!logBuffer.hasRemaining()) {
			throw new IllegalAccessError(String.format("解析列时出问题，没有可用的数据。colName:%s,colType:%s", colName, colType));
		}
		int length = 0;
		if (colType == ColumnType.STRING) {// TODO 没有测试
			if (meta >= 256) {
				final int meta0 = meta >> 8;
				final int meta1 = meta & 0xFF;
				if ((meta0 & 0x30) != 0x30) { // a long CHAR() field: see #37426
					colType = ColumnType.get(0xFF & (meta0 | 0x30));
					length = meta1 | (((meta0 & 0x30) ^ 0x30) << 4);
				} else {
					ColumnType meta0Type = ColumnType.get(0xFF & meta0);
					switch (meta0Type) {
					case SET:
					case ENUM:
					case STRING:
						colType = ColumnType.get(0xFF & meta0);
						length = meta1;
						break;
					default:
						throw new RuntimeException("不支持的类型: " + colType);
					}
				}
			} else {
				length = meta;
			}
		}
		// ICol retobj = null;
		switch (colType) {
		case TINY:// 1
			int val = logBuffer.getInt8(); // ByteUtil.readIntL(assitRead.readBytes(1));
			// retobj =
			// ColInteger.builder().colName(colName).colType(colType).value(val).build();
			return new ColInteger(val, colType, colName);
		case SHORT:// 2
			int val2 = logBuffer.getInt16(); // ByteUtil.readIntL(assitRead.readBytes(2));
			// retobj =
			// ColInteger.builder().colName(colName).colType(colType).value(val2).build();
			return new ColInteger(val2, colType, colName);
		case INT24:// 9
			int val3 = logBuffer.getInt24();// ByteUtil.readIntL(assitRead.readBytes(3));
			// retobj =
			// ColInteger.builder().colName(colName).colType(colType).value(val3).build();
			return new ColInteger(val3, colType, colName);
		case LONG:// 3
			long val4 = logBuffer.getInt32();// ByteUtil.readLongL(assitRead.readBytes(4));
			// retobj =
			// ColLong.builder().colName(colName).colType(colType).value(val4).build();
			return new ColLong(val4, colType, colName);
		case LONGLONG:// 8
			long val5 = logBuffer.getLong64(); // ByteUtil.readLongL(assitRead.readBytes(8));
			// retobj =
			// ColLong.builder().colName(colName).colType(colType).value(val5).build();
			return new ColLong(val5, colType, colName);
		case FLOAT:// 4
			float val6 = this.logBuffer.getFloat32();
			// retobj =
			// ColFloat.builder().colName(colName).colType(colType).value(val6).build();
			return new ColFloat(val6, colType, colName);
		case DOUBLE:// 5
			double val7 = this.logBuffer.getDouble64();
			// retobj =
			// ColDouble.builder().colName(colName).colType(colType).value(val7).build();
			return new ColDouble(val7, colType, colName);
		case YEAR:// 13
			int val8 = this.logBuffer.getUint8();
			// retobj =
			// ColInteger.builder().colName(colName).colType(colType).value(val8
			// + 1900).build();
			return new ColInteger(val8, colType, colName);
		case DATE:// 10
			int i3211 = this.logBuffer.getUint24();
			if (this.cal == null)
				this.cal = Calendar.getInstance();
			this.cal.clear();
			this.cal.set(i3211 / 512, i3211 / 32 % 16 - 1, i3211 % 32);
			// retobj = ColDateSql.builder().colName(colName).colType(colType)
			// .value(new java.sql.Date(this.cal.getTimeInMillis())).build();
			return new ColDateSql(new java.sql.Date(this.cal.getTimeInMillis()), colType, colName);
		case TIME:// 11
			int i321 = this.logBuffer.getUint24();
			if (this.cal == null)
				this.cal = Calendar.getInstance();
			this.cal.clear();
			this.cal.set(70, 0, 1, i321 / 10000, i321 % 10000 / 100, i321 % 100);
			// retobj =
			// ColTimeSql.builder().colName(colName).colType(colType).value(new
			// Time(this.cal.getTimeInMillis()))
			// .build();
			return new ColTimeSql(new Time(this.cal.getTimeInMillis()), colType, colName);
		case DATETIME:// 12
			long i64 = this.logBuffer.getLong64();
			int d = (int) (i64 / 1000000L);
			int t = (int) (i64 % 1000000L);
			if (this.cal == null)
				this.cal = Calendar.getInstance();
			this.cal.clear();
			this.cal.set(d / 10000, d % 10000 / 100 - 1, d % 100, t / 10000, t % 10000 / 100, t % 100);
			// retobj = ColDateTime.builder().colName(colName).colType(colType)
			// .value(new Timestamp(this.cal.getTimeInMillis())).build();
			return new ColDateTime(new Timestamp(this.cal.getTimeInMillis()), colType, colName);
		case TIMESTAMP:// 7
			long i32 = this.logBuffer.getUint32();
			// retobj =
			// ColTimestamp.builder().colName(colName).colType(colType).value(new
			// Timestamp(i32 * 1000L)).build();
			return new ColTimestamp(new Timestamp(i32 * 1000L), colType, colName);
		case ENUM:// 247
			int int32 = 0;
			switch (length) {
			case 1:
				int32 = this.logBuffer.getUint8();
				break;
			case 2:
				int32 = this.logBuffer.getUint16();
				break;
			default:
				throw new IllegalArgumentException("!! Unknown ENUM packlen = " + length);
			}
			log.warn("MYSQL_TYPE_ENUM : This enumeration value is only used internally and cannot exist in a binlog!");
			// retobj =
			// ColInteger.builder().colName(colName).colType(colType).value(int32).build();
			return new ColInteger(int32, colType, colName);
		case SET:// 248
			byte[] nbits1 = new byte[length];
			this.logBuffer.fillBytes(nbits1, 0, length);
			log.warn("MYSQL_TYPE_SET : This enumeration value is only used internally and cannot exist in a binlog!");
			long val14 = ByteUtil.readLongL(nbits1);
			// retobj =
			// ColLong.builder().colName(colName).colType(colType).value(val14).build();
			return new ColLong(val14, colType, colName);
		case BIT:// 16
			int nbits = (meta >> 8) * 8 + (meta & 0xFF);
			length = (nbits + 7) / 8;
			if (nbits > 1) {
				byte[] bits = new byte[length];
				this.logBuffer.fillBytes(bits, 0, length);
				// retobj =
				// ColBytes.builder().colName(colName).colType(colType).value(bits).build();
				return new ColBytes(bits, colType, colName);
			} else {
				Integer bit = this.logBuffer.getInt8();
				// retobj =
				// ColBytes.builder().colName(colName).colType(colType).value(new
				// byte[] { bit.byteValue() })
				// .build();
				return new ColBytes(new byte[] { bit.byteValue() }, colType, colName);
			}
		case BLOB:// 252
			byte[] ret = null;
			switch (meta) {
			case 1:
				int len8 = this.logBuffer.getUint8();
				ret = new byte[len8];
				this.logBuffer.fillBytes(ret, 0, len8);
				break;
			case 2:
				int len16 = this.logBuffer.getUint16();
				ret = new byte[len16];
				this.logBuffer.fillBytes(ret, 0, len16);
				break;
			case 3:
				int len24 = this.logBuffer.getUint24();
				ret = new byte[len24];
				this.logBuffer.fillBytes(ret, 0, len24);
				break;
			case 4:
				int len32 = (int) this.logBuffer.getUint32();
				ret = new byte[len32];
				this.logBuffer.fillBytes(ret, 0, len32);
				break;
			default:
				throw new IllegalArgumentException("!! Unknown BLOB packlen = " + meta);
			}
			// retobj =
			// ColBytes.builder().colName(colName).colType(colType).value(ret).build();
			return new ColBytes(ret, colType, colName);
		case NEWDECIMAL:// 246
			int precision = meta & 0xFF;
			int decimals = meta >> 8;
			BigDecimal bd = this.logBuffer.getDecimal(precision, decimals);
			// retobj =
			// ColBigDecimal.builder().colName(colName).colType(colType).value(bd).build();
			return new ColBigDecimal(bd, colType, colName);
		case STRING:// 254
			length = length < 256 ? this.logBuffer.getUint8() : this.logBuffer.getUint16();
			// retobj = ColString.builder().colName(colName).colType(colType)
			// .value(this.logBuffer.getFullString(length,
			// this.charsetName)).build();
			return new ColString(this.logBuffer.getFullString(length, this.charsetName), colType, colName);
		case VARCHAR:// 15
		case VAR_STRING:// 253
			length = meta < 256 ? this.logBuffer.getUint8() : this.logBuffer.getUint16();
			// retobj = ColString.builder().colName(colName).colType(colType)
			// .value(this.logBuffer.getFullString(length,
			// this.charsetName)).build();
			return new ColString(this.logBuffer.getFullString(length, this.charsetName), colType, colName);
		case TIME_V2:// 19
			long intpart1 = 0L;
			int frac1 = 0;
			long ltime = 0L;
			switch (meta) {
			case 0:
				intpart1 = this.logBuffer.getBeUint24() - 8388608L;
				ltime = intpart1 << 24;
				break;
			case 1:
			case 2:
				intpart1 = this.logBuffer.getBeUint24() - 8388608L;
				frac1 = this.logBuffer.getUint8();
				if ((intpart1 < 0L) && (frac1 > 0)) {
					intpart1 += 1L;
					frac1 -= 256;
				}

				ltime = intpart1 << 24 + frac1 * 10000;
				break;
			case 3:
			case 4:
				intpart1 = this.logBuffer.getBeUint24() - 8388608L;
				frac1 = this.logBuffer.getBeUint16();
				if ((intpart1 < 0L) && (frac1 > 0)) {
					intpart1 += 1L;
					frac1 -= 65536;
				}

				ltime = intpart1 << 24 + frac1 * 100;
				break;
			case 5:
			case 6:
				intpart1 = this.logBuffer.getBeUlong48() - 8388608L;
				ltime = intpart1;
				break;
			default:
				intpart1 = this.logBuffer.getBeUint24() - 8388608L;
				ltime = intpart1 << 24;
			}

			long ultime = Math.abs(ltime);
			intpart1 = ultime >> 24;
			if (this.cal == null)
				this.cal = Calendar.getInstance();
			this.cal.clear();
			this.cal.set(70, 0, 1, (int) ((intpart1 >> 12) % 1024L), (int) ((intpart1 >> 6) % 64L),
					(int) (intpart1 % 64L));
			// retobj =
			// ColTimeSql.builder().colName(colName).colType(colType).value(new
			// Time(this.cal.getTimeInMillis()))
			// .build();
			return new ColTimeSql(new Time(this.cal.getTimeInMillis()), colType, colName);
		case DATETIME_V2:// 18
			final long value2 = this.logBuffer.readLongB(5);
			final int nanos2 = (int) this.logBuffer.readLongB((meta + 1) / 2);
			Date val21 = MysqlUtil.toDatetime2(value2, nanos2);
			// retobj =
			// ColDateTime.builder().colName(colName).colType(colType).value(val21).build();
			return new ColDateTime(val21, colType, colName);
		case TIMESTAMP_V2:// 17
			long tv_sec = this.logBuffer.getBeUint32();
			int tv_usec = 0;
			switch (meta) {
			case 0:
				tv_usec = 0;
				break;
			case 1:
			case 2:
				tv_usec = this.logBuffer.getInt8() * 10000;
				break;
			case 3:
			case 4:
				tv_usec = this.logBuffer.getBeInt16() * 100;
				break;
			case 5:
			case 6:
				tv_usec = this.logBuffer.getBeInt24();
				break;
			default:
				tv_usec = 0;
			}

			Timestamp time = new Timestamp(tv_sec * 1000L);
			time.setNanos(tv_usec * 1000);
			// retobj =
			// ColTimestamp.builder().colName(colName).colType(colType).value(time).build();
			return new ColTimestamp(time, colType, colName);
		default:
			log.error("不支持的类型：[{}]", colType.name());
			return null;
		}
		// return retobj;
	}

	///////////////////////////////////////////////////////////////////////////////
	public static String row(Long num) {
		String startstr = String.format(IBinlogRead.BIN_ROW, num, Long.toHexString(num));
		return startstr;
	}

	public static String row(Integer num) {
		String startstr = String.format(IBinlogRead.BIN_ROW, num, Integer.toHexString(num));
		return startstr;
	}

}
